#ifndef COMMON_H_
#define COMMON_H_

#include <string>
#include <vector>
#include <deque>
#include <set>
#include <map>
#include <algorithm>
#include <limits>
#include <iostream>
#include <sstream>
#include <fstream>
#include <stdexcept>

#include <tr1/unordered_set>
#include <tr1/unordered_map>

#include <assert.h>
#include <sys/time.h>

namespace utilities
{

class BlockTimer
{
public:
    static unsigned long get_tick_count()
    {
		timeval ts;
		gettimeofday(&ts, 0);
		return ((unsigned long)(ts.tv_sec * 1000 + (ts.tv_usec / 1000)));
    }

public:
    BlockTimer(const std::string& description) : description_(description), start_time_(get_tick_count()) {}

    BlockTimer() : description_("Block execution time"), start_time_(get_tick_count()) {}

    ~BlockTimer()
    {
    	std::clog << description_ << ": " << (get_tick_count()-start_time_) << " milliseconds\n";
    }

private:
    std::string description_;
    unsigned long start_time_;
};

class ParametricString
{
public:
	ParametricString(const std::string& body) : body_(body)
	{
	}

	std::string str() { return body_; }

	template<typename T>
	ParametricString& arg(const std::string& name, const T& value)
	{
		std::size_t pos=body_.find(name);
		if(pos!=std::string::npos)
		{
			std::ostringstream output; output << value;
			body_.replace(pos, name.size(), output.str());
		}
		return (*this);
	}

	template<typename T>
	ParametricString& arg(const T& value)
	{
		return arg("{}", value);
	}

private:
	std::string body_;
};

template<typename T>
inline T virtual_value()
{
	return std::numeric_limits<T>::max();
}

template<>
inline std::string virtual_value<std::string>()
{
	return std::string("virtual");
}

template<typename T>
inline T null_value()
{
	return (std::numeric_limits<T>::max()-1);
}

template<>
inline std::string null_value<std::string>()
{
	return std::string("null");
}

template<typename MapType>
static std::deque<typename MapType::key_type> collect_map_keys(const MapType& map_obj)
{
	std::deque<typename MapType::key_type> keys;
	for(typename MapType::const_iterator it=map_obj.begin();it!=map_obj.end();it++)
	{
		keys.push_back(it->first);
	}
	return keys;
}

class Table
{
public:
	Table(std::istream& input) :
		header_(read_row(input)),
		header_map_(row_map(header_)),
		rows_(read_rows(input, header_.size()))
	{
	}

	int columns_count() const { return header_.size(); }

	int rows_count() const { return rows_.size(); }

	bool column_exists(const std::string& column_name) const { return (column_index(column_name)>=0); }

	template<typename T>
	T value(const int row, const std::string& column_name) const
	{
		if(row<0 || row>=rows_count()) { alarm_invalid_row_index(row); }
		const int column=column_index(column_name);
		if(column<0 || column>=columns_count()) { throw std::invalid_argument(utilities::ParametricString("Function {} misses column {}").arg(__FUNCTION__).arg(column_name).str()); }
		std::istringstream input(rows_[row][column]);
		T v; input >> v;
		return v;
	}

private:
	typedef std::vector<std::string> Row;

	static Row read_row(std::istream& input)
	{
		std::string line_string;
		std::getline(input, line_string);
		std::istringstream line_stream(line_string);
		Row row;
		while(line_stream.good())
		{
			std::string val; line_stream >> val;
			if(!val.empty()) { row.push_back(val); }
		}
		return row;
	}

	static std::map<std::string, int> row_map(const Row& row)
	{
		std::map<std::string, int> positions;
		for(std::size_t i=0;i<row.size();i++) { positions[row[i]]=i; }
		return positions;
	}

	static std::vector<Row> read_rows(std::istream& input, std::size_t row_length)
	{
		std::vector<Row> rows;
		bool terminated=false;
		while(input.good() && !terminated)
		{
			const Row row=read_row(input);
			if(row.size()==1 && row.front()=="end.") { terminated=true; }
			else if(row.size()==row_length) { rows.push_back(row); }
		}
		return rows;
	}

	int column_index(const std::string& column_name) const
	{
		std::map<std::string, int>::const_iterator it=header_map_.find(column_name);
		if(it!=header_map_.end()) { return (it->second); }
		else { return -1; }
	}

	static void alarm_invalid_row_index(const int row)
	{
		std::ostringstream output;
		output << "Invalid row index " << row;
		throw std::invalid_argument(output.str());
	}

	static void alarm_invalid_column_name(const std::string& column_name)
	{
		std::ostringstream output;
		output << "Invalid column name " << column_name;
		throw std::invalid_argument(output.str());
	}

private:
	Row header_;
	std::map<std::string, int> header_map_;
	std::vector<Row> rows_;
};

class CommandLineOptions
{
public:
	CommandLineOptions(const int argc, const char** argv)
	{
		for(int i=0;i<argc;i++)
		{
			const std::string str(argv[i]);
			if(str.find("--")==0) { options_[str]=""; }
			else if(i>0)
			{
				const std::string prev_str(argv[i-1]);
				if(prev_str.find("--")==0) { options_[prev_str]=str; }
			}
		}
	}

	bool isopt(const std::string& name) const { return (options_.find(name)!=options_.end()); }

	bool isarg(const std::string& name) const { return (isopt(name) && !options_.find(name)->second.empty()); }

	template<typename T>
	T arg(const std::string& name) const
	{
		if(!isarg(name)) { throw std::invalid_argument(utilities::ParametricString("Missing command line option '{}'").arg(name).str()); }
		std::istringstream input(options_.find(name)->second);
		T value; input >> value;
		return value;
	}

private:
	std::map<std::string, std::string> options_;
};

}

#endif /* COMMON_H_ */
